// 跟心跳超时相关的函数实现
#include <unistd.h>

#include "global.h"
#include "my_memory.h"
#include "my_lock.h"
#include "net/socket.h"
#include "net/netglobal.h"
#include "log.h"


// 设置踢出时钟，当三次握手成功兵开启超时检测就会调用本函数
void CSocket::AddToTimerQueue(lpconnection_t pConn){
    CMemory *p_memory = CMemory::GetInstance();

    time_t futtime = time(NULL);
    futtime += m_iWaitTime;// 从配置文件读取，目前是20秒

    // 初始化消息头
    LPSTRUC_MSG_HEADER tmpMsgHeader = (LPSTRUC_MSG_HEADER)p_memory->AllocMemory(m_iLenMsgHeader, false);
    tmpMsgHeader->pConn = pConn;
    tmpMsgHeader->iCurrsequence = pConn->iCurrsequence;
    // 操作m_timeQueuemap容器获取锁
    CLock lock(&m_timequeueMutex);
    //按键值自动排序 小->大，使用make_pair是为了兼容旧版本C++
    m_timerQueuemap.insert(std::make_pair(futtime, tmpMsgHeader)); 
    m_cur_size_++;// 时间队列加一
    m_timer_value_ = GetEarliestTime();// 取出时间队列头部的时间的值
}

// 从时间队列取出最早的时间并返回，由调用者负责互斥
time_t CSocket::GetEarliestTime(){
    std::multimap<time_t, LPSTRUC_MSG_HEADER>::iterator pos;
    pos = m_timerQueuemap.begin();// 指向第一个元素
    return pos->first;// 返回第一个元素的键
}

// 从时间队列移除最早的时间，并把最早时间所在项的值对应的指针返回，由调用者负责互斥
LPSTRUC_MSG_HEADER CSocket::RemoveFirstTimer(){
    std::multimap<time_t, LPSTRUC_MSG_HEADER>::iterator pos;
    LPSTRUC_MSG_HEADER p_tmp;
    // 如果时间队列是空的
    if(m_cur_size_ <= 0){
        return NULL;
    }
    // 调用者保证互斥，这里直接操作
    pos = m_timerQueuemap.begin();
    p_tmp = pos->second;
    m_timerQueuemap.erase(pos);
    --m_cur_size_;
    return p_tmp;
}

// 根据给定的时间，从时间队列找到比这个时间更老（超时）的一个项并返回，由调用者负责互斥
LPSTRUC_MSG_HEADER CSocket::GetOverTimeTimer(time_t cur_time){
    CMemory *p_memory = CMemory::GetInstance();
    LPSTRUC_MSG_HEADER ptmp;

    // 队列为空直接返回
    if(m_cur_size_ == 0 || m_timerQueuemap.empty()){
        return NULL;
    }

    // 判断队列里最新的时间是否比参数时间旧，是才说明肯定有超时的节点
    time_t earliesttime = GetEarliestTime();
    if(earliesttime <= cur_time){
        ptmp = RemoveFirstTimer();// 移除最旧的项并返回
        // m_ifTimeOutKick值为0不主动断开；1主动断开
        if(m_ifTimeOutKick != 1){
            // 说明要服务端不主动断开，要周期性检查，更新超时时间后重新放回队列（旧的返回给删除函数统一释放）
            time_t newinqueutime = cur_time+(m_iWaitTime);
            LPSTRUC_MSG_HEADER tmpMsgHeader = (LPSTRUC_MSG_HEADER)p_memory->AllocMemory(sizeof(STRUC_MSG_HEADER),false);
            tmpMsgHeader->pConn = ptmp->pConn;
            tmpMsgHeader->iCurrsequence = ptmp->iCurrsequence;
            m_timerQueuemap.insert(std::make_pair(newinqueutime, tmpMsgHeader)); //自动排序 小->大
            m_cur_size_++;
        }

        // 重新获取最旧的时间
        if(m_cur_size_ > 0){
            m_timer_value_ = GetEarliestTime();
        }
        return ptmp;
    }
    return NULL;// 否则说明没有超时的节点
}

// 将指定用户的TCP连接从时间队列中移除
void CSocket::DeleteFromTimerQueue(lpconnection_t pConn){
    std::multimap<time_t, LPSTRUC_MSG_HEADER>::iterator pos,posend;
    CMemory *p_memory = CMemory::GetInstance();
    // 操作时间队列获取锁
    CLock lock(&m_timequeueMutex);
    
    // 考虑到情况会比较复杂，所以遍历整个队列，而不是找到一次就返回
    pos = m_timerQueuemap.begin();
    posend = m_timerQueuemap.end();
    while(pos != posend){
        if(pos->second->pConn == pConn){
            p_memory->FreeMemory(pos->second);// 释放内存
            pos = m_timerQueuemap.erase(pos);// 删除元素
            --m_cur_size_;// 长度自减
        }
        else{
            pos++;
        }
    }

    // 获取队列中的最旧值
    if(m_cur_size_ > 0)
	{
		m_timer_value_ = GetEarliestTime();
	}
}

// 清理时间队列所有内容,这个时候程序结束，不需要互斥了
void CSocket::ClearAllFromTimerQueue(){
    std::multimap<time_t, LPSTRUC_MSG_HEADER>::iterator pos,posend;
    CMemory *p_memory = CMemory::GetInstance();
    pos = m_timerQueuemap.begin();
    posend = m_timerQueuemap.end();
    for(; pos != posend; ++pos)	
	{
		p_memory->FreeMemory(pos->second);		
		--m_cur_size_; 		
	}
	m_timerQueuemap.clear();
}

// 时间队列的监控线程（处理踢出到期不发心跳包的用户）
void* CSocket::ServerTimerQueueMonitorThread(void *threadData){
    ThreadItem *pThread = static_cast<ThreadItem*>(threadData);
    CSocket *pSocketObj = pThread->_pThis;

    time_t absolute_time,cur_time;
    int err;

    // 系统结束前一直循环
    while(g_stopEvent == 0){
        // 没有使用互斥判断，只是一个初级判断，目的至少是队列为空时避免系统损耗，引入互斥又太浪费
        if(pSocketObj->m_cur_size_ > 0){
            absolute_time = pSocketObj->m_timer_value_;// 相当于省了一个互斥，很划算
            cur_time = time(NULL);
            //时间到了，可以进行处理
            if(absolute_time < cur_time){
                std::list<LPSTRUC_MSG_HEADER> m_lsIdleList;// 临时超时队列
                LPSTRUC_MSG_HEADER result;
                // 加锁
                err = pthread_mutex_lock(&pSocketObj->m_timequeueMutex);
                if(err != 0){
                    Log_Error(err,"CSocket::ServerTimerQueueMonitorThread()中pthread_mutex_lock()失败，返回的错误码为%d!",err);
                }
                // 一次性处理所有超时节点
                while((result = pSocketObj->GetOverTimeTimer(cur_time)) != NULL){
                    m_lsIdleList.push_back(result); 
                }
                // 释放锁
                err = pthread_mutex_unlock(&pSocketObj->m_timequeueMutex); 
                if(err != 0){
                    Log_Error(err,"CSocket::ServerTimerQueueMonitorThread()pthread_mutex_unlock()失败，返回的错误码为%d!",err);
                }

                // 处理临时超时队列
                LPSTRUC_MSG_HEADER tmpmsg;
                while(!m_lsIdleList.empty()){
                    tmpmsg = m_lsIdleList.front();
					m_lsIdleList.pop_front(); 
                    // 心跳超时处理，基类函数只是释放内存，子类应该继承重写
                    pSocketObj->ProcPingTimeOutChecking(tmpmsg, cur_time);
                }
            }
        }
        usleep(500 * 1000); //为简化问题，我们直接每次休息500毫秒
    }
    return (void*)0;
}

// 心跳包检测时间到，处理心跳包是否超时的问题，基类函数只是释放内存，子诶应该继承重写
void CSocket::ProcPingTimeOutChecking(LPSTRUC_MSG_HEADER tmpmsg, time_t cur_time){
    CMemory *p_memory = CMemory::GetInstance();
    p_memory->FreeMemory(tmpmsg);
}